Skip to content

fix(privacy): segfault due to use-after-free from get_children()#5114

Open
jaschiu wants to merge 2 commits into
Alexays:masterfrom
jaschiu:patch-3
Open

fix(privacy): segfault due to use-after-free from get_children()#5114
jaschiu wants to merge 2 commits into
Alexays:masterfrom
jaschiu:patch-3

Conversation

@jaschiu

@jaschiu jaschiu commented Jun 9, 2026

Copy link
Copy Markdown

Hi, waybar was crashing for me when I was using the privacy module. My LLM wrote the below description for me and debugged it for me.

Summary

Waybar segfaults on startup when the privacy module is configured. The crash is a use-after-free in Privacy::update() caused by iterating the transient C++ wrappers returned by Gtk::Container::get_children().

Steps to reproduce

Minimal config:

{
    "modules-right": ["privacy"],
}

Start waybar; it crashes shortly after the bar is configured.

Crash

Thread 1 "waybar" received signal SIGSEGV, Segmentation fault.
__dynamic_cast () from /usr/lib/.../libstdc++.so.6
#1  waybar::modules::privacy::Privacy::update () at src/modules/privacy/privacy.cpp:152
#2  Bar::getModules(...)::<lambda()> at src/bar.cpp:572
#3  Glib::DispatchNotifier::pipe_io_handler(...)
...

The crash is in dynamic_cast<PrivacyItem*>(widget) while looping over box_.get_children().

Root cause

Privacy::update() iterated the children of box_ and dynamic_cast-ed each one back to PrivacyItem*:

for (Gtk::Widget* widget : box_.get_children()) {
  auto* module = dynamic_cast<PrivacyItem*>(widget);
  ...
}

Gtk::Container::get_children() returns a std::vector<Gtk::Widget*> built by wrapping each child GtkWidget via Glib::wrap() over a shallowly-owned GList. Rather than returning the original PrivacyItem wrappers, it can hand back freshly-created wrappers that are freed immediately, so the pointers in the returned vector dangle. Dereferencing them in dynamic_cast reads a garbage vtable and crashes.

Confirmed under gdb: the PrivacyItem objects created in the constructor remain alive with valid vtables, but get_children() returns a different, already-freed pointer:

CTOR item=0x...acc160   (valid PrivacyItem vtable 0x...9968c8)
CTOR item=0x...a42300   (valid PrivacyItem vtable 0x...9968c8)
AT UPDATE:
  orig i0=0x...acc160  vtable_now=0x...9968c8   (still valid)
  orig i1=0x...a42300  vtable_now=0x...9968c8   (still valid)
  get_children widget=0x...ab85c0  vtable=0x...aff130   (dangling -> crash)

This is the same class of issue already fixed for the niri workspaces module, which stopped relying on get_children() wrappers.

Fix

Stop round-tripping through get_children() + dynamic_cast. The PrivacyItems are owned by box_ (added via Gtk::make_managed) and stay valid for the lifetime of the module, so we store the pointers we create and iterate those directly.

  • Add std::vector<PrivacyItem*> modules_; to Privacy.
  • Push each PrivacyItem* into modules_ when it is created in the constructor.
  • Iterate modules_ in update() instead of box_.get_children(), removing the now-unnecessary dynamic_cast/null check.

Testing

  • Built locally and ran under gdb with a minimal privacy-only config.
  • Before: deterministic SIGSEGV in Privacy::update on the first update.
  • After: bar configures and runs normally; privacy items reveal/hide correctly with no crash.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant